package com.google.appengine.codelab;

import static com.google.appengine.api.datastore.FetchOptions.Builder.withLimit;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
//import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.CompositeFilterOperator;
import com.google.appengine.api.datastore.Query.Filter;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.FilterPredicate;


@SuppressWarnings("serial")
public class UserStory12Servlet extends BaseServlet {
	public void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		super.doGet(req, resp);
		insertEntities();
		PrintWriter out = resp.getWriter();
	    out.println(consulta3());
	    return;
	}
	
	private String consulta3() {
		DatastoreService ds = Util.getDatastoreServiceInstance();
		Query query = new Query("Fraude");
		long total = ds.prepare(query).countEntities(withLimit(1000));
		long c1 = countFraudeBetweenValores(new Float(0), new Float(500));
		long c2 = countFraudeBetweenValores(new Float(500.01), new Float(2000));
		long c3 = countFraudeBetweenValores(new Float(2000.01), new Float(5000));
		long c4 = countFraudeBetweenValores(new Float(5000.01), new Float(0));
		String s = "{\"data\": [{\"interval1\" : \"" +
		        "%d - %.2f%%\",\"interval2\" : \"" +
		        "%d - %.2f%%\",\"interval3\" : \"" +
		        "%d - %.2f%%\",\"interval4\" : \"" +
		        "%d - %.2f%%\"}]}";
		return String.format(s,
				c1, new Float((c1 * 100) / (double) total),
				c2, new Float((c2 * 100) / (double) total),
				c3, new Float((c3 * 100) / (double) total),
				c4, new Float((c4 * 100) / (double) total));
	}
	
	private void insertEntities() {
		Localidade.createOrUpdateLocalidade("1", new Float(-13.58), new Float(-51.85), "", "", "", "", "");
		Localidade.createOrUpdateLocalidade("2", new Float(-15.78), new Float(-47.92), "", "", "", "", "");
		
		Cliente.createOrUpdateCliente("1", "joao", "Joao", "Costa", "", "", "", "1");
		Cliente.createOrUpdateCliente("2", "jose", "JOSE", "SILVA", "", "", "", "1");
		Cliente.createOrUpdateCliente("3", "maria", "MARIA", "BENEDITA", "", "", "", "1");
		
		Estabelecimento.createOrUpdateEstabelecimento("1", "", "ESTABELECIMENTO1", "1");
		Estabelecimento.createOrUpdateEstabelecimento("2", "", "ESTABELECIMENTO2", "2");
		
		Midia.createOrUpdateMidia("1", "MIDIA", "", "", "", "1");
		Midia.createOrUpdateMidia("2", "MIDIA", "", "", "", "2");
		Midia.createOrUpdateMidia("3", "MIDIA", "", "", "", "3");
		
		Transacao.createOrUpdateTransacao("1", "", new Double(10), "20130501", "", "1", "1", "1", "1");
		Transacao.createOrUpdateTransacao("2", "", new Double(10), "20130501", "", "1", "2", "2", "1");
		Transacao.createOrUpdateTransacao("3", "", new Double(500.1), "20130501", "", "1", "2", "2", "1");
		Transacao.createOrUpdateTransacao("4", "", new Double(25), "20130501", "", "1", "2", "2", "1");
		Transacao.createOrUpdateTransacao("5", "", new Double(80), "20130501", "", "1", "3", "3", "1");
		Transacao.createOrUpdateTransacao("6", "", new Double(500), "20130501", "", "1", "3", "3", "2");
		
		Fraude.createOrUpdateFraude("1", "FRAUDE1", "TIPO1", "", "", "2");
		Fraude.createOrUpdateFraude("2", "FRAUDE2", "TIPO", "", "", "3");
		Fraude.createOrUpdateFraude("3", "FRAUDE3", "TIPO1", "", "", "6");
	}
	
	
	  
	public static long countFraudeBetweenValores(Float iniValue, Float finValue) {
		FiltroFaixaValores filtrosView = new FiltroFaixaValores();
		Iterable<Entity> transacoes = getTransacoesBetweenValores(iniValue, finValue, filtrosView);
		long count = 0;
		for (Entity transacao : transacoes) {
			if (transacao != null) {
				count += getFraudesByTransId((String)transacao.getProperty("trans_id"),filtrosView).countEntities(withLimit(1000));
			}
	    }
		return count;
	}
	  
	public static long countFraudeByLocalidadeBetweenValores(Float latitude, Float longitude,
			Float iniValue, Float finValue) {
		Iterable<Entity> localidades = getLocalidadeByLatitudeLongitude(latitude, longitude);
		long count = 0;
		for (Entity localidade : localidades) {
			if (localidade != null) {
				Iterable<Entity> transacoes = getTransacoesByLocalidadeBetweenValores(
						(String)localidade.getProperty("localidade_id"), iniValue, finValue);
				for (Entity transacao : transacoes) {
					if (transacao != null) {
						count += getFraudesByTransId((String)transacao.getProperty("trans_id"),new FiltroFaixaValores()).countEntities(withLimit(1000));
					}
				}
			}
		}
		return count;
	}
	  
	private static PreparedQuery getFraudesByTransId(String trans_id, FiltroFaixaValores filtro) {
		DatastoreService ds = Util.getDatastoreServiceInstance();
		Query query = new Query("Fraude");
		List<Filter> filtros = new ArrayList<Filter>();
		
		filtros.add( new FilterPredicate("trans_id", FilterOperator.EQUAL, trans_id) );
		
		if( ! filtro.getFraude_type().isEmpty() ){
			filtros.add( new FilterPredicate("fraude_type", FilterOperator.EQUAL, filtro.getFraude_type()) );
		}
		
		if( filtro.getFraude_data_inicial() != null ){
			filtros.add( new FilterPredicate("fraude_data_deteccao", FilterOperator.GREATER_THAN_OR_EQUAL, Common.dateToInt(filtro.getFraude_data_inicial())  ) );
		}
		
		if( filtro.getFraude_data_final() != null && filtro.getFraude_data_final().after(filtro.getFraude_data_inicial())  ){
			filtros.add( new FilterPredicate("fraude_data_deteccao", FilterOperator.LESS_THAN_OR_EQUAL, Common.dateToInt(filtro.getFraude_data_final()) ) );
		}	
		
		query.setFilter( filtros.size() == 1 ? filtros.get(0) : CompositeFilterOperator.and(filtros) );
		return ds.prepare(query);
	}
	
	public static Iterable<Entity> getFraudeByType(String fraude_type) {
		return Util.listEntities("Fraude", "fraude_type", fraude_type);
	}
	
	public static Iterable<Entity> getLocalidadeByLatitudeLongitude(Float latitude,
			Float longitude) {
		DatastoreService ds = Util.getDatastoreServiceInstance();
		Query query = new Query("Localidade");
		Filter f1 = new FilterPredicate("localidade_latitude", FilterOperator.EQUAL, latitude);
		Filter f2 = new FilterPredicate("localidade_longitude", FilterOperator.EQUAL, longitude);
		Filter fc = CompositeFilterOperator.and(f1, f2);
		query.setFilter(fc);
		return ds.prepare(query).asIterable();
	}
	
	public static List<String> getNomeClienteByFraudeBetweenValores(Float iniValue,
			Float finValue) {
		DatastoreService ds = Util.getDatastoreServiceInstance();
		Iterable<Entity> transacoes = getTransacoesBetweenValores(iniValue, finValue, new FiltroFaixaValores());
		List<String> result = new ArrayList<String>();
		for (Entity transacao : transacoes) {
			if (transacao != null) {
				Query query = new Query("Fraude");
				Filter filter = new FilterPredicate("trans_id", FilterOperator.EQUAL, 
						transacao.getKey());
				query.setFilter(filter);
				if (ds.prepare(query).countEntities(withLimit(1000)) != 0) {
					query = new Query("Cliente");
					filter = new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, 
							FilterOperator.EQUAL, transacao.getProperty("cliente_id"));
					query.setFilter(filter);
					Entity cliente = ds.prepare(query).asSingleEntity();
					if (cliente != null) {
						String s = cliente.getProperty("cliente_firstName") + " " +
					        cliente.getProperty("cliente_lastName");
						if (!result.contains(s)) result.add(s);
					}
				}
			}
	    }
		return result;
	}
	
	public static List<Entity> getTransacoesBetweenValores(Float iniValue, Float finValue, FiltroFaixaValores filtrosView) 
	{
		DatastoreService ds = Util.getDatastoreServiceInstance();
		Query query = new Query("Transacao");
		List<Filter> filtros = new ArrayList<Filter>();
		
		filtros.add( new FilterPredicate("trans_price", FilterOperator.GREATER_THAN_OR_EQUAL, iniValue) );
		if (finValue != null && finValue >= iniValue){
			filtros.add( new FilterPredicate("trans_price", FilterOperator.LESS_THAN_OR_EQUAL, finValue) );
		}
		
		if( ! filtrosView.getTrans_type().isEmpty() ){
			filtros.add( new FilterPredicate("trans_type", FilterOperator.EQUAL, filtrosView.getTrans_type() ));
		}
		
		query.setFilter( filtros.size() == 1 ? filtros.get(0) : CompositeFilterOperator.and(filtros) );
		List<Entity> transacoes = new ArrayList<Entity>();
		
		for( Entity transacao : ds.prepare(query).asIterable() )
		{
			if( filtrosView.getTrans_data_inicial() != null && Common.dateToInt(filtrosView.getTrans_data_inicial()) > (Integer.parseInt((String)transacao.getProperty("trans_data")))){
				continue;
			}
			if( filtrosView.getTrans_data_final() != null ){
				Integer dataFinal = Common.dateToInt(filtrosView.getTrans_data_final());
				if( dataFinal > Common.dateToInt(filtrosView.getTrans_data_inicial()) && dataFinal < (Integer.parseInt((String)transacao.getProperty("trans_data"))) ){
					continue;
				}
			}

			Entity localidade = Localidade.getSingleLocalidade((String) transacao.getProperty("localidade_id"));
			
			if( ! filtrosView.getPais().isEmpty() && ! filtrosView.getPais().equals(localidade.getProperty("localidade_country"))){
				continue;
			}

			if( ! filtrosView.getEstado().isEmpty() && ! filtrosView.getEstado().equals(localidade.getProperty("localidade_state"))){
				continue;
			}

			if( ! filtrosView.getCidade().isEmpty() && ! filtrosView.getCidade().equals(localidade.getProperty("localidade_city"))){
				continue;
			}
			
			Entity midia = Midia.getSingleMidia((String) localidade.getProperty("midia_id") );
			
			if( ! filtrosView.getMidia_bandeira().isEmpty() && ! midia.getProperty("midia_bandeira").toString().equals(filtrosView.getMidia_bandeira())  ){
				continue;
			}
			
			transacoes.add(transacao);
		}
		
		return transacoes;
	}
	  
	public static Iterable<Entity> getTransacoesByLocalidadeBetweenValores(String localidade_id,
			Float iniValue, Float finValue) {
		DatastoreService ds = Util.getDatastoreServiceInstance();
		Query query = new Query("Transacao");
		Filter f1 = new FilterPredicate("trans_price", FilterOperator.GREATER_THAN_OR_EQUAL,
				iniValue);
		Filter f2 = new FilterPredicate("trans_price", FilterOperator.LESS_THAN_OR_EQUAL,
				finValue);
		Filter f3 = new FilterPredicate("localidade_id", FilterOperator.EQUAL, localidade_id);
		Filter fc = CompositeFilterOperator.and(f1, f2, f3);
		query.setFilter(fc);
		return ds.prepare(query).asIterable();
	}
	
	/**
	 * @author cassiano
	 * 
	 * Requirement: "The system shall filter frauds by multiple parameters."
	 * Specification: filter by type, fraud date, transaction date, transaction type, 
	 *                card brand, geographic location and values range;
	 */
	public static List<Entity> getFraudesByParameters(String fraude_type, 
			String Fraude_data_inicial, String fraude_data_final, String trans_data_inicial, 
			String trans_data_final, String trans_type, String midia_bandeira, 
			String pais, String estado, String cidade, String trans_price_inicial, String trans_price_final) {
		

		FiltroFaixaValores filtrosView = new FiltroFaixaValores(fraude_type, 
				getData(Fraude_data_inicial), 
				getData(fraude_data_final), 
				getData(trans_data_inicial), 
				getData(trans_data_final), 
				trans_type, midia_bandeira, pais, estado, cidade);
		
		Iterable<Entity> transacoes = getTransacoesBetweenValores(new Float(trans_price_inicial), new Float(trans_price_final), filtrosView);
		List<Entity> result = new ArrayList<Entity>();
		
		for (Entity transacao : transacoes) {
			result.addAll( getFraudesByTransId((String)transacao.getProperty("trans_id"),filtrosView).asList(FetchOptions.Builder.withDefaults()) );
	    }
		
		return result;
	}
	
	private static Date getData(String data){
		if( data == null || data.isEmpty() ){
			return null;
		}
		return Common.cDate(data);
	}
	
	/**
	 * @author cassiano
	 * 
	 * Testing requisite: "The system shall get transaction information by filtered frauds"
	 * Specification: return transactions related to the given fraud list
	 */
	public static Iterable<Entity> getTransacoesByFraudesList(Iterable<Entity> fraudes) {
		
		//TODO
		
		return null;
	}
	
}
